package com.hzy.utils;

import com.hzy.exception.MappingException;
import io.netty.util.internal.StringUtil;
import io.vertx.core.Future;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.RowSet;
import io.vertx.sqlclient.SqlConnection;
import io.vertx.sqlclient.Tuple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.time.*;
import java.util.*;
import java.util.function.Function;

public class QueryUtil {

    static final Logger log = LoggerFactory.getLogger(QueryUtil.class);

    static Map<Class<?>, Function<String, Object>> converters = new HashMap<>();

    public static void registerConverters() {
        converters.put(String.class, s -> s);
        converters.put(boolean.class, Boolean::parseBoolean);
        converters.put(Boolean.class, Boolean::valueOf);

        converters.put(byte.class, Byte::parseByte);
        converters.put(Byte.class, Byte::valueOf);

        converters.put(short.class, Short::parseShort);
        converters.put(Short.class, Short::valueOf);

        converters.put(int.class, Integer::parseInt);
        converters.put(Integer.class, Integer::valueOf);

        converters.put(long.class, Long::parseLong);
        converters.put(Long.class, Long::valueOf);

        converters.put(float.class, Float::parseFloat);
        converters.put(Float.class, Float::valueOf);

        converters.put(double.class, Double::parseDouble);
        converters.put(Double.class, Double::valueOf);

        converters.put(LocalDate.class, LocalDate::parse);
        converters.put(LocalTime.class, LocalTime::parse);
        converters.put(LocalDateTime.class, LocalDateTime::parse);
        converters.put(ZonedDateTime.class, ZonedDateTime::parse);
        converters.put(Duration.class, Duration::parse);
        converters.put(ZoneId.class, ZoneId::of);
    }


    /**
     * 查询数据
     *
     * @param sql 查询 SQL 语句
     * @return 包含实体列表的 Future
     */
    public static Future<RowSet<Row>> query(String sql) {
        if (StringUtil.isNullOrEmpty(sql)) {
            throw new IllegalArgumentException("Input parameters cannot be null");
        }
        return DbUtils.client.preparedQuery(sql).execute();
    }


    /**
     * 查询数据并将结果映射为实体列表
     *
     * @param sql 查询 SQL 语句
     * @return 包含实体列表的 Future
     */
    public static Future<RowSet<Row>> query(String sql, Tuple params) {
        if (StringUtil.isNullOrEmpty(sql) || params == null) {
            throw new IllegalArgumentException("Input parameters cannot be null");
        }
        return DbUtils.client.preparedQuery(sql).execute(params);
    }

    /**
     * 查询数据并将结果映射为实体列表
     *
     * @param sql    查询 SQL 语句
     * @param params 查询参数
     * @param clazz  实体类的 Class 对象
     * @param <T>    实体类类型
     * @return 包含实体列表的 Future
     */
    public static <T> Future<List<T>> query(String sql, Tuple params, Class<T> clazz) {
        if (StringUtil.isNullOrEmpty(sql) || params == null || clazz == null) {
            throw new IllegalArgumentException("Input parameters cannot be null");
        }

        return DbUtils.client.preparedQuery(sql).execute(params)
                .map(result -> mapRowsToEntities(result, clazz));
    }

    /**
     * 将查询结果映射为实体列表
     *
     * @param result 查询结果
     * @param clazz  实体类的 Class 对象
     * @param <T>    实体类类型
     * @return 实体列表
     */
    private static <T> List<T> mapRowsToEntities(RowSet<Row> result, Class<T> clazz) {
        List<T> entities = new ArrayList<>();
        Field[] fields = clazz.getDeclaredFields();

        for (Row row : result) {
            try {
                T entity = createEntity(clazz, row, fields);
                entities.add(entity);
            } catch (Exception e) {
                throw new MappingException("Failed to map row to entity", e);
            }
        }
        return entities;
    }

    /**
     * 创建单个实体对象
     *
     * @param clazz  实体类的 Class 对象
     * @param row    查询结果中的单行数据
     * @param fields 实体类的字段数组
     * @param <T>    实体类类型
     * @return 实体对象
     * @throws Exception 如果创建实体失败
     */
    private static <T> T createEntity(Class<T> clazz, Row row, Field[] fields) throws Exception {
        T entity = null;
        if (Map.class.isAssignableFrom(clazz)) {
            if (clazz == Map.class) {
                entity = (T) new HashMap<>();
            } else {
                Constructor<T> constructor = clazz.getDeclaredConstructor();
                constructor.setAccessible(true);
                entity = constructor.newInstance();
            }
            populateMapEntity((Map<String, Object>) entity, row);
        } else {
            Constructor<T> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            entity = constructor.newInstance();
            populateObjectEntity(entity, row, fields);
        }
        return entity;
    }

    /**
     * 填充 Map 类型的实体
     *
     * @param entity Map 类型的实体
     * @param row    查询结果中的单行数据
     */
    private static <T extends Map<String, Object>> void populateMapEntity(T entity, Row row) {
        // 参数校验
        if (entity == null || row == null || converters == null) {
            throw new IllegalArgumentException("参数 entity、row 或 converters 不能为空");
        }

        // 获取列名并填充实体
        int columnIndex = 0;
        while (true) {
            String columnName = row.getColumnName(columnIndex);
            if (StringUtil.isNullOrEmpty(columnName)) {
                break;
            }
            handleColumn(entity, row, columnName);
            columnIndex++;
        }

    }

    /**
     * 处理单个列的数据
     *
     * @param entity     Map 类型的实体
     * @param row        查询结果中的单行数据
     * @param columnName 列名
     */
    private static <T extends Map<String, Object>> void handleColumn(T entity, Row row, String columnName) {
        Object value = row.getValue(columnName);
        if (value == null) {
            log.debug("Column '{}' value is null.", columnName);
            return;
        }

        Function<String, Object> converter = converters.get(value.getClass());
        if (converter == null) {
            entity.put(NamingConverterUtil.snakeToCamel(columnName), value);
        } else {
            entity.put(NamingConverterUtil.snakeToCamel(columnName), converter.apply(String.valueOf(value)));
        }
    }

    /**
     * 填充普通对象类型的实体
     *
     * @param entity 实体对象
     * @param row    查询结果中的单行数据
     * @param fields 实体类的字段数组
     * @throws IllegalAccessException 如果访问字段失败
     */
    private static void populateObjectEntity(Object entity, Row row, Field[] fields) throws IllegalAccessException {
        for (Field field : fields) {
            field.setAccessible(true);
            String columnName = field.getName();
            Object value = getValueFromRow(row, columnName, NamingConverterUtil.camelToSnake(columnName));
            if (value != null) {
                Function<String, Object> converter = converters.get(field.getType());
                field.set(entity, converter != null ? converter.apply(String.valueOf(value)) : value);
            }
        }
    }

    /**
     * 从 Row 中获取值，支持蛇形命名和驼峰命名
     *
     * @param row       查询结果中的单行数据
     * @param camelName 驼峰命名的列名
     * @param snakeName 蛇形命名的列名
     * @return 列值，若两列均不存在则返回 null
     */
    private static Object getValueFromRow(Row row, String camelName, String snakeName) {
        Object value = tryGetValue(row, camelName);
        if (value != null) {
            return value;
        }

        value = tryGetValue(row, snakeName);
        if (value != null) {
            return value;
        }
        int size = row.size();
        if (size == 0) {
            return null;
        }
        List<String> columnNames = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            columnNames.add(row.getColumnName(i));
        }

        String lowerCamelName = camelName.toLowerCase();
        String upperCamelName = camelName.toUpperCase();
        String lowerSnakeName = snakeName.toLowerCase();
        String upperSnakeName = snakeName.toUpperCase();

        for (String colName : columnNames) {
            String lowerColName = colName.toLowerCase();
            String upperColName = colName.toUpperCase();

            if (lowerColName.equals(lowerCamelName) || lowerColName.equals(lowerSnakeName)) {
                return tryGetValue(row, colName);
            }
            if (upperColName.equals(upperCamelName) || upperColName.equals(upperSnakeName)) {
                return tryGetValue(row, colName);
            }
        }

        return null;
    }

    /**
     * 尝试从 Row 中获取指定列的值，并处理异常
     *
     * @param row  查询结果中的单行数据
     * @param name 列名
     * @return 列值，若列不存在则返回 null
     */
    private static Object tryGetValue(Row row, String name) {
        try {
            return row.getValue(name);
        } catch (NoSuchElementException e) {
            log.debug("Column '{}' not found in row", name);
        } catch (Exception e) {
            log.error("Unexpected error while getting value for column '{}': {}", name, e.getMessage(), e);
        }
        return null;
    }

    /**
     * 【事务专用】在指定的数据库连接上执行查询，并将结果映射为实体列表
     *
     * @param connection 事务连接
     * @param sql        查询 SQL 语句
     * @param params     查询参数
     * @param clazz      实体类的 Class 对象
     * @param <T>        实体类类型
     * @return 包含实体列表的 Future
     */
    public static <T> Future<List<T>> query(SqlConnection connection, String sql, Tuple params, Class<T> clazz) {
        if (connection == null || StringUtil.isNullOrEmpty(sql) || params == null || clazz == null) {
            throw new IllegalArgumentException("Input parameters cannot be null");
        }

        return connection.preparedQuery(sql).execute(params)
                .map(result -> mapRowsToEntities(result, clazz));
    }

    /**
     * 【事务专用】在指定的数据库连接上执行更新/插入/删除等操作
     *
     * @param connection 事务连接
     * @param sql        执行的 SQL 语句
     * @param params     参数
     * @return 包含结果的 Future
     */
    public static Future<RowSet<Row>> execute(SqlConnection connection, String sql, Tuple params) {
        if (connection == null || StringUtil.isNullOrEmpty(sql) || params == null) {
            throw new IllegalArgumentException("Input parameters cannot be null");
        }
        return connection.preparedQuery(sql).execute(params);
    }
}